﻿#region License
// <copyright file="PrimaryKeySequenceAttribute.cs" company="Kopigi - Marc Plessis">
// Copyright © Kopigi 2011
// </copyright>
// ****************************************************************************
// <author>Marc Plessis</author>
// <email>marc.plessis@kopigi.fr</email>
// <date>03/04/2011</date>
// <project>Biwo</project>
// <web>http://www.kopigi.fr/biwo</web>
// <license>
// See license.txt in this solution or http://www.microsoft.com/opensource/licenses.mspx
// </license>
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Reflection;
using System.ComponentModel.DataAnnotations;

namespace SQLServer.Repository.POCO.Generated
{
    [AttributeUsage(AttributeTargets.Property)]
    public class PrimaryKeySequenceAttribute : Attribute
    {
        #region properties
        public FonctionSequence FonctionSequence = FonctionSequence.None;
        public int Increment = -1;
        public PrefixMask PrefixMask = PrefixMask.None;
        public string PropertyMaskName = "";
        public string SequenceOracleName = "";
        #endregion

        public PrimaryKeySequenceAttribute() { }

        /// <summary>
        /// Retrieve PrimariesKeySequenceAttribute in Entity
        /// </summary>
        /// <returns></returns>
        public static void GetPrimariesKeySequenceAttribute<T>(T entity, out Dictionary<PrimaryKeySequenceAttribute, PropertyInfo> propertiesInfo)
        {
            propertiesInfo = new Dictionary<PrimaryKeySequenceAttribute, PropertyInfo>();

            var metadataTypeAttributes =
                entity.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>();

            if (metadataTypeAttributes.Count() > 0)
            {
                foreach (MetadataTypeAttribute metadataTypeAttribute in metadataTypeAttributes)
                {
                    foreach (MemberInfo memberInfo in metadataTypeAttribute.MetadataClassType.GetMembers())
                    {
                        object[] attributes =
                            memberInfo.GetCustomAttributes(typeof(PrimaryKeySequenceAttribute), true);
                        if (attributes.Length > 0)
                        {
                            if (attributes.First() is PrimaryKeySequenceAttribute)
                                propertiesInfo.Add(attributes.First() as PrimaryKeySequenceAttribute, typeof(T).GetProperty(memberInfo.Name));
                        }
                    }
                }
            }
        }


        public static object GetId<T>(PrimaryKeySequenceAttribute primaryKeyAttribute, PropertyInfo primaryKeyInfo, IObjectSet<T> objectSet, T entity, ObjectContext context, object prefixPrimaryKey = null) where T : class
        {
            object _id = null;
            if (primaryKeyAttribute != null)
            {
                if (primaryKeyAttribute.FonctionSequence != FonctionSequence.None)
                {
                    T ent = null;
                    if (primaryKeyAttribute.PrefixMask != PrefixMask.None)
                    {
                        PropertyInfo prop = typeof(T).GetProperty(primaryKeyInfo.Name);
                        switch (primaryKeyAttribute.PrefixMask)
                        {
                            case PrefixMask.PropertyEntity:
                                var valueProperty = GetValueProperty(entity, primaryKeyAttribute.PropertyMaskName);
                                if (prop != null)
                                {
                                    ent = GetEntity<T>(p => prop.GetValue(p, null).ToString().StartsWith(valueProperty.ToString()), primaryKeyAttribute.FonctionSequence, objectSet) ??
                                           GetEntityEntriesAdded<T>(p => prop.GetValue(p, null).ToString().StartsWith(valueProperty.ToString()), primaryKeyAttribute.FonctionSequence, context, EntityState.Added);
                                }
                                break;

                            case PrefixMask.Argument:
                                if (prefixPrimaryKey != null)
                                {
                                    ent = GetEntity<T>(p => prop.GetValue(p, null).ToString().StartsWith(prefixPrimaryKey.ToString()), primaryKeyAttribute.FonctionSequence, objectSet) ??
                                           GetEntityEntriesAdded<T>(p => prop.GetValue(p, null).ToString().StartsWith(prefixPrimaryKey.ToString()), primaryKeyAttribute.FonctionSequence, context, EntityState.Added);
                                }
                                break;
                        }
                    }
                    else
                    {
                        if (primaryKeyAttribute.FonctionSequence != FonctionSequence.SequenceOracle)
                        {
                            ent = GetEntity(primaryKeyAttribute.FonctionSequence, objectSet) ??
                                   GetEntityEntriesAdded<T>(primaryKeyAttribute.FonctionSequence, context,
                                                            EntityState.Added);
                        }
                        else
                        {
                            //Execute sequence from Oracle
                            return GetValueSequence(primaryKeyAttribute.SequenceOracleName, context);
                        }
                    }

                    dynamic vLeft = primaryKeyInfo.PropertyType.IsValueType
                                         ? (ent != null
                                                ? primaryKeyInfo.GetValue(ent, null)
                                                : Convert.ChangeType(Activator.CreateInstance(primaryKeyInfo.PropertyType),
                                                                     primaryKeyInfo.PropertyType))
                                         : (ent != null
                                                ? primaryKeyInfo.GetValue(ent, null)
                                                : Convert.ChangeType(String.Empty, primaryKeyInfo.PropertyType));
                    dynamic vRight = Convert.ChangeType(primaryKeyAttribute.Increment, primaryKeyInfo.PropertyType);
                    _id = vLeft + vRight;
                }
            }
            return _id;
        }

        private static object GetValueProperty<T>(T entity, string propertyName)
        {
            PropertyInfo propertyInfo = entity.GetType().GetProperties().Where(m => m.Name.Equals(propertyName)).First();
            if (propertyInfo != null) return propertyInfo.GetValue(entity, null);
            return null;
        }

        private static T GetEntity<T>(FonctionSequence sequence, IEnumerable<T> objectSet) where T : class
        {
            switch (sequence)
            {
                case FonctionSequence.Min:
                    return objectSet.AsEnumerable().Min();
                case FonctionSequence.Max:
                    return objectSet.AsEnumerable().Max();
            }
            return null;
        }

        private static T GetEntity<T>(Func<T, bool> predicate, FonctionSequence sequence, IEnumerable<T> objectSet) where T : class
        {
            switch (sequence)
            {
                case FonctionSequence.Min:
                    return objectSet.AsEnumerable().Where(predicate).Min();
                case FonctionSequence.Max:
                    return objectSet.AsEnumerable().Where(predicate).Max();
            }
            return null;
        }

        private static T GetEntityEntriesAdded<T>(FonctionSequence sequence, ObjectContext context, EntityState entityState) where T : class
        {
            switch (sequence)
            {
                case FonctionSequence.Max:
                    return (context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(objectStateEntry => objectStateEntry.Entity as T)).AsEnumerable().Max();
                case FonctionSequence.Min:
                    return (context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(objectStateEntry => objectStateEntry.Entity as T)).AsEnumerable().Min();
            }
            return null;
        }

        private static T GetEntityEntriesAdded<T>(Func<T, bool> predicate, FonctionSequence sequence, ObjectContext context, EntityState entityState) where T : class
        {
            switch (sequence)
            {
                case FonctionSequence.Max:
                    return (context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(objectStateEntry => objectStateEntry.Entity as T)).AsEnumerable().Where(predicate).Max();
                case FonctionSequence.Min:
                    return (context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(objectStateEntry => objectStateEntry.Entity as T)).AsEnumerable().Where(predicate).Min();
            }
            return null;
        }

        /// <summary>
        /// Calculate next value of specified sequence
        /// </summary>
        /// <param name="sequenceName">Name Oracle's sequence</param>
        /// <param name="context">Context of Entity Framework</param>
        /// <returns></returns>
        private static object GetValueSequence(string sequenceName, ObjectContext context)
        {
            string commandText = "select " + sequenceName + ".nextval FROM DUAL";
            var ret = context.ExecuteStoreQuery<object>(commandText).First();
            return ret ?? 0;
        }
    }

    /// <summary>
    /// Enumeration forcalculate sequence increment of primary keys
    /// </summary>
    public enum FonctionSequence
    {
        None = -1,
        Max = 0,
        Min = 1,
        SequenceOracle = 2
    }

    public enum PrefixMask
    {
        None = -1,
        PropertyEntity = 0,
        Argument = 1
    }
}
